using System;

namespace Needle.Utils {

    /// <summary>
    /// Defines a specifiction defined for type T
    /// </summary>
    [Serializable]
    public abstract class Specification<T> {

        #region public methods
        /// <summary>
        /// Returns true if the item satisfies the specification.
        /// </summary>
        public abstract bool IsSatisfied(T item);

        /// <summary>
        /// Boolean and operator.
        /// </summary>
        public Specification<T> And(Specification<T> other) {
            return this & other;
        }

        /// <summary>
        /// boolean or operator.
        /// </summary>
        public Specification<T> Or(Specification<T> other) {
            return this | other;
        }
        /// <summary>
        /// Boolean exclusive or operator.
        /// </summary>
        public Specification<T> Xor(Specification<T> other) {
            return this ^ other;
        }
        /// <summary>
        /// Boolean negation operator.
        /// </summary>
        public Specification<T> Not() {
            return !this;
        }

        #endregion

        #region private classes

        [Serializable]
        private class AndSpecification : Specification<T>
        {
            Specification<T> lhs;
            Specification<T> rhs;

            public AndSpecification(Specification<T> lhs, Specification<T> rhs) {
                if (lhs == null || rhs == null) {
                    throw new ArgumentNullException();
                }
                this.lhs = lhs;
                this.rhs = rhs;
            }

            public override bool IsSatisfied(T item) {
                return lhs.IsSatisfied(item) && rhs.IsSatisfied(item);
            }

        }

        [Serializable]
        private class OrSpecification : Specification<T>
        {

            Specification<T> lhs;
            Specification<T> rhs;

            public OrSpecification(Specification<T> lhs, Specification<T> rhs) {
                if (lhs == null || rhs == null) {
                    throw new ArgumentNullException();
                }
                this.lhs = lhs;
                this.rhs = rhs;
            }

            public override bool IsSatisfied(T item) {
                return lhs.IsSatisfied(item) || rhs.IsSatisfied(item);
            }

        }

        [Serializable]
        private class XorSpecification : Specification<T>
        {

            Specification<T> lhs;
            Specification<T> rhs;

            public XorSpecification(Specification<T> lhs, Specification<T> rhs) {
                if (lhs == null || rhs == null) {
                    throw new ArgumentNullException();
                }
                this.lhs = lhs;
                this.rhs = rhs;
            }

            public override bool IsSatisfied(T item) {
                return lhs.IsSatisfied(item) ^ rhs.IsSatisfied(item);
            }

        }

        [Serializable]
        private class NotSpecification : Specification<T>
        {

            Specification<T> c;

            public NotSpecification(Specification<T> c) {
                if (c == null) {
                    throw new ArgumentNullException();
                }
                this.c = c;
            }

            public override bool IsSatisfied(T item) {
                return !c.IsSatisfied(item);
            }

        }
        #endregion

        #region operators

        /// <summary>
        /// Not operator overload.
        /// </summary>
        public static Specification<T> operator !(Specification<T> c) {
            return new NotSpecification(c);
        }
        /// <summary>
        /// And operator overload.
        /// </summary>
        public static Specification<T> operator &(Specification<T> a, Specification<T> b) {
            return new AndSpecification(a, b);
        }
        /// <summary>
        /// Or operator overload
        /// </summary>
        public static Specification<T> operator |(Specification<T> a, Specification<T> b) {
            return new OrSpecification(a, b);
        }
        /// <summary>
        /// Exclusive Or operator overload
        /// </summary>
        public static Specification<T> operator ^(Specification<T> a, Specification<T> b) {
            return new XorSpecification(a, b);
        }

        #endregion
    }

}
